package com.atguigu.gmall.activity.service.impl;

import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.service.SeckillGoodsService;
import com.atguigu.gmall.activity.util.CacheHelper;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.model.activity.OrderRecode;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.atguigu.gmall.model.activity.UserRecode;
import com.atguigu.gmall.model.order.OrderDetail;
import org.junit.jupiter.api.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Author atguigu-mqx
 * @Description TODO
 * @Date 2023/5/31 9:20
 * @Version 1.0
 */
@Service
public class SeckillGoodsServiceImpl implements SeckillGoodsService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RabbitService rabbitService;

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;
    @Override
    public List<SeckillGoods> findAll() {
        //  查询所有：hvals key;
        List<SeckillGoods> seckillGoodsList = redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).values();
        return seckillGoodsList;
    }

    @Override
    public SeckillGoods getSeckillGoodsBySkuId(Long skuId) {
        //  hget key field;
        SeckillGoods seckillGoods = (SeckillGoods) this.redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).get(skuId.toString());
        return seckillGoods;
    }

    /**
     * 预下单抢购
     * @param skuIdStr  页面传递的抢购码
     * @param userId    为了生产抢购码与页面传递的抢购码做校验
     * @param skuId     判断状态位
     * @return
     */
    @Override
    public Result seckillOrder(String skuIdStr, String userId, Long skuId) {
        //  校验状态位
        if (!skuIdStr.equals(MD5.encrypt(userId))){
            return Result.fail().message("校验抢购码失败");
        }
        //  校验商品的状态位. 存储在本地内存: map中 key=skuId value=0/1;
        String status = (String) CacheHelper.get(skuId.toString());
        if (StringUtils.isEmpty(status) || "0".equals(status)){
            return Result.fail().message("状态位异常或商品已售罄");
        }
        //  剩下的状态位只能为1，1是可以秒杀的。
        UserRecode userRecode = new UserRecode();
        userRecode.setUserId(userId);
        userRecode.setSkuId(skuId);
        //  将这个对象发送到队列中.
        this.rabbitService.sendMsg(MqConst.EXCHANGE_DIRECT_SECKILL_USER ,MqConst.ROUTING_SECKILL_USER,userRecode);
        //  返回预下单成功.
        return Result.ok();
    }

    /**
     * 预下单监听数据.
     * @param userRecode
     */
    @Override
    public void seckillUser(UserRecode userRecode) {
        //  校验商品的状态位. 存储在本地内存: map中 key=skuId value=0/1;
        String status = (String) CacheHelper.get(userRecode.getSkuId().toString());
        if (StringUtils.isEmpty(status) || "0".equals(status)){
            return;
        }
        //  判断这个用户是否已经秒杀过. 借助于redis; setnx key value;
        //  userKey = seckill:user:userId:skuId
        String userKey = RedisConst.SECKILL_USER+userRecode.getUserId()+":"+userRecode.getSkuId();
        //  第一次执行返回true ,第二次执行false;
        Boolean skuIdExist = this.redisTemplate.opsForValue().setIfAbsent(userKey, userRecode.getSkuId(),RedisConst.SECKILL__TIMEOUT, TimeUnit.SECONDS);
        if (!skuIdExist){
            // 返回
            return;
        }
        //  秒杀减库存-- redis-list
        String skuId = (String) this.redisTemplate.opsForList().rightPop(RedisConst.SECKILL_STOCK_PREFIX + userRecode.getSkuId());
        //  此时说明当前没有商品剩余库存.
        if (StringUtils.isEmpty(skuId)){
            //  同时通知其他兄弟节点.
            this.redisTemplate.convertAndSend("seckillpush",userRecode.getSkuId()+":0");
            return;
        }

        //  需要将预下单数据保存到 redis 中.
        //  key = seckill:orders field=userId value=userRecode; userRecode{userId,skuId} ---> {必须要通过skuId 去缓存获取数据。}
        //  public class OrderRecode { seckillGoods, num }
        OrderRecode orderRecode = new OrderRecode();
        orderRecode.setUserId(userRecode.getUserId());
        //  限制每个人只能抢购一件商品.
        orderRecode.setNum(1);
        orderRecode.setOrderStr(MD5.encrypt(userRecode.getUserId()+userRecode.getSkuId()));
        orderRecode.setSeckillGoods(this.getSeckillGoodsBySkuId(userRecode.getSkuId()));
        //  key = seckill:orders field=userId value=orderRecode;
        this.redisTemplate.opsForHash().put(RedisConst.SECKILL_ORDERS,userRecode.getUserId(),orderRecode);

        //  发送异步消息，实现减库存.
        this.rabbitService.sendMsg(MqConst.EXCHANGE_DIRECT_SECKILL_STOCK,MqConst.ROUTING_SECKILL_STOCK,userRecode.getSkuId());

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateStock(Long skuId) {
        //  更新两个，mysql ,redis 修改商品的剩余库存数.
        //  获取剩余数量
        Long count = this.redisTemplate.opsForList().size(RedisConst.SECKILL_STOCK_PREFIX + skuId);
        //  先获取到更新哪个商品
        SeckillGoods seckillGoods = (SeckillGoods) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_GOODS, skuId.toString());
        seckillGoods.setStockCount(count.intValue());
        // mysql
        seckillGoodsMapper.updateById(seckillGoods);
        // redis
        this.redisTemplate.opsForHash().put(RedisConst.SECKILL_GOODS, skuId.toString(),seckillGoods);
    }

    /**
     * 检查抢购状态.
     * @param skuId
     * @param userId
     * @return
     */
    @Override
    public Result checkOrder(Long skuId, String userId) {
        //  1.  判断这个用户在缓存中是否存在！
        String userKey = RedisConst.SECKILL_USER+userId+":"+skuId;
        Boolean userKeyExist = this.redisTemplate.hasKey(userKey);
        //  用户key 存在
        if (userKeyExist){
            //  2. 判断是否有预下单记录.
            //  UserRecode userRecode = (UserRecode) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_ORDERS, userId);
              OrderRecode orderRecode = (OrderRecode) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_ORDERS, userId);
            if (null != orderRecode){
                //  此时是抢购成功.去下单.
                return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);
            }
        }
        //  判断用户是否有真正下单记录，如果有真正的下单记录。则提示去查看我的订单！ 真正下单之后，也会在缓存中存储一个数据。
        //  真正下过订单， hset key field value; key=seckill:orders:users field=userId value=orderId;
        String orderKey = RedisConst.SECKILL_ORDERS_USERS;
        String orderId = (String) this.redisTemplate.opsForHash().get(orderKey, userId);
        if (null != orderId){
            //  此时是抢购成功.去看我订单.
            return Result.build(orderId, ResultCodeEnum.SECKILL_ORDER_SUCCESS);
        }

        //  校验状态.
        String status = (String) CacheHelper.get(skuId.toString());
        if (StringUtils.isEmpty(status) || "0".equals(status)){
            return Result.build(null,ResultCodeEnum.SECKILL_FAIL);
        }
        return Result.build(null,ResultCodeEnum.SECKILL_RUN);
    }

    @Override
    public Result<Map> trade(String userId) {
        //  创建map 集合对象
        HashMap<String, Object> map = new HashMap<>();
        //  获取缓存预下单的key。
        //  key = seckill:orders field=userId value=orderRecode;
        OrderRecode orderRecode = (OrderRecode) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_ORDERS, userId);
        //   UserRecode userRecode = (UserRecode) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_ORDERS, userId);
        //   this.getSeckillGoodsBySkuId(userRecode.getSkuId());
        //  秒杀对象：
        SeckillGoods seckillGoods = orderRecode.getSeckillGoods();
        OrderDetail orderDetail = new OrderDetail();
        orderDetail.setSkuId(seckillGoods.getSkuId());
        orderDetail.setSkuName(seckillGoods.getSkuName());
        orderDetail.setSkuNum(orderRecode.getNum());
        orderDetail.setImgUrl(seckillGoods.getSkuDefaultImg());
        orderDetail.setOrderPrice(seckillGoods.getCostPrice());
        ArrayList<OrderDetail> detailArrayList = new ArrayList<>();
        detailArrayList.add(orderDetail);
        //  秒杀的商品.
        map.put("detailArrayList",detailArrayList);
        map.put("totalNum",orderRecode.getNum());
        map.put("totalAmount",seckillGoods.getCostPrice());
        //  返回map 集合。
        return Result.ok(map);
    }
}
